/* HW2_starter_app - ECE 544 Homework #2 Application template
 *
 * Copyright (c) 2012, 2013 Roy Kravitz.  All rights reserved.
 *
 * Author:		Jabeer Ahmed, Jyotsna Shridharan
 * Date:		05-22-13
 * Revision:	1.0
 *
 * Revision History:
 * -----------------
 *  22-May-2013		JA		<your comment>
 *
 * 	Description:
 * 	------------
 * 	This program implements ECE 544 Homework 2.  It is a Xilinx-based design that is meant to be a learning tool
 * 	for incorporating several of the key functions provided by Xilkernel.   The application sets up 4 independent
 * 	threads.  Thread 1 periodically reads the switches on the Nexys 3 board and sends an update to the LED thread.
 * 	Thread 2 responds to pushbutton press interrupts and sends an update to the LED thread whenever the state of
 * 	any of the pushbuttons on the Nexys3 board change.   Thread 3 writes the new pushbutton and switch state to the
 * 	LEDs.   The three threads communicate via a message queue.  Thread 1 communicates to its interrupt handler through
 * 	the use of a semaphore.  The application also makes use of the Xiline watchdog timer.
 * 	The 4th thread (the master thread) sets up the Microblaze, initializes the peripherals, creates the other threads and
 * 	the semaphore and then enters a main loop where it wakes up periodically to reset the WDT.  Status messages are sent
 * 	via STDOUT through the USB serial port which can be connected to a PC running a terminal emulator such as putty.
 *
 * <your additional comments>
 *
 */

// Includes
#include "xmk.h"
#include "os_config.h"
#include "config/config_param.h"
#include "sys/ksched.h"
#include "sys/init.h"
#include <pthread.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <semaphore.h>
#include <sys/intr.h>
#include <sys/timer.h>
#include <sys/types.h>
#include <stdio.h>
#include <errno.h>

#include "xparameters.h"
#include "platform_config.h"
#include "platform.h"
#include "stdbool.h"
#include "xgpio.h"
#include "xwdttb.h"
#include "xtmrctr.h"
#include "xstatus.h"
#include "xintc.h"

// Declarations
#define BTN_GPIO_DEVICEID		XPAR_PUSH_BUTTONS_4BITS_DEVICE_ID
#define SW_GPIO_DEVICEID		XPAR_DIP_SWITCHES_8BITS_DEVICE_ID
#define LED_GPIO_DEVICEID		XPAR_LEDS_8BITS_DEVICE_ID
#define INTC_DEVICEID			XPAR_XPS_INTC_0_DEVICE_ID
#define WDT_DEVICEID			XPAR_XPS_TIMEBASE_WDT_0_DEVICE_ID
#define TMRCTR0_DEVICEID		XPAR_XPS_TIMER_0_DEVICE_ID
#define TMRCTR1_DEVICEID		XPAR_XPS_TIMER_1_DEVICE_ID

#define TMRCTR0_INTR_NUM		XPAR_XPS_INTC_0_XPS_TIMER_0_INTERRUPT_INTR
#define TMRCTR1_INTR_NUM		XPAR_XPS_INTC_0_XPS_TIMER_1_INTERRUPT_INTR
#define BTN_GPIO_INTR_NUM		XPAR_XPS_INTC_0_PUSH_BUTTONS_4BITS_IP2INTC_IRPT_INTR
#define WDT_INTR_NUM			XPAR_XPS_INTC_0_XPS_TIMEBASE_WDT_0_WDT_INTERRUPT_INTR

#define XPIO_CHANNEL  1

// macro functions
#define MIN(a, b)  				( ((a) <= (b)) ? (a) : (b) )
#define MAX(a, b)  				( ((a) >= (b)) ? (a) : (b) )

XGpio BTNInst, SWInst, LEDInst;
XTmrCtr TMRCTR1Inst;
XWdtTb WDTInst;
XIntc IntrCtlr;

// Message passing variables
typedef struct					// LED message definition
{
	int msg_src;
	int msg_value;
} t_LED_message, *t_LED_messageptr;

t_LED_message newMessagequeue;  //new message queue
const int led_msg_key = 1;		// message key for LED message queue
struct msqid_ds	led_msgstats;	// statistics from message queue

// Synchronization variables
sem_t btn_press_sema;			// semaphore between clock tick ISR and the clock main thread
volatile u32 btn_state;			// button state - shared between button handler and button thread

// Flags for Threads
static bool isApplicationRunning = false;
static bool isButtonThreadRunning = false;
static bool isLEDThreadRunning = false;
static bool isSwitchesThreadRunning = false;
static bool forceFreeze = false;

static u32 button_state = 0;
static int indicate_error;                  // indicate error due to msg function

// Function declarations
void* master_thread(void *arg);
void* button_thread(void *arg);
void* switches_thread(void *arg);
void* leds_thread(void *arg);
void  button_handler(void);
void  wdt_handler(void);
XStatus init_peripherals(void);

int main()
{
    XStatus sts;
    // initialize the platform and the peripherals
    init_platform();
    sts = init_peripherals();
    if (sts != XST_SUCCESS)
    {
    	xil_printf("FATAL ERROR: Could not initialize the peripherals\n\r");
		xil_printf("Please power cycle or reset the system\n\r");
		return -1;
    }
    
	// check if WDT expired and caused the reset - if so, don't start
	if (XWdtTb_IsWdtExpired(&WDTInst))
	{
	 	// it's true, the WDT expired.
        xil_printf("Watch Dog timer expired 0\n\r");
    	if (XWdtTb_IsWdtExpired(&WDTInst))
    	{
    		XWdtTb_Initialize(&WDTInst, WDT_DEVICEID);
            xil_printf("Watch Dog timer expired 1\n\r");
        	if (XWdtTb_IsWdtExpired(&WDTInst))
        	{
        		XWdtTb_Initialize(&WDTInst, WDT_DEVICEID);
            	if (XWdtTb_IsWdtExpired(&WDTInst))
            	{
                    xil_printf("Watch Dog timer expired 2\n\r");
                    xil_printf("Sleeping for 5 sec\n\r");
            	}
        	}
        }
    }


    // Initialize xilkernel
    xilkernel_init();

    // Create the master thread
    xmk_add_static_thread(master_thread, 0);
    
    // Start the kernel
    xilkernel_start();
    
    // Should never be reached
    cleanup_platform();
    
    return 0;
}

// The master thread
void* master_thread(void *arg)
{
    pthread_t button;
    pthread_t switches;
    pthread_t leds;

    pthread_attr_t attr;
    struct sched_param spar;

    int ret;

    xil_printf("----------------------------------------------------------------------------\r\n");
    xil_printf("ECE 544 Homework 2 Starter App (Spring 2013)\r\n");
    xil_printf("----------------------------------------------------------------------------\r\n");
    xil_printf("This Xilkernel based application reads the buttons and switches on the Digilent \r\n"
               "Nexys 3 board and displays them on the LEDs.  Even though the application is\r\n"
               "simple it uses sever of the synchronization and interprocess communication\r\n"
               "capabilities offered in the Xilkernel\r\n\r\n"
               "To demonstrate, press any of the buttons and/or flip switches[3:0] (the rightmost).\r\n"
               "The current state of the buttons and switches should be displayed on the LEDs\r\n");
    xil_printf("----------------------------------------------------------------------------\r\n\r\n\r\n");;

    xil_printf("MASTER: Master Thread Starting\r\n");

    // set the priority of all but the master thread to 1.  The master thread runs at priority 0
    // because it is responsible for tickling the WDT.
    pthread_attr_init (&attr);              
    spar.sched_priority = 1;
    pthread_attr_setschedparam(&attr, &spar);

    // create the threads
    ret = pthread_create (&button, &attr, (void*) button_thread, NULL);
    if (ret != 0)
    {
    	xil_printf("ERROR (%d) IN MASTER THREAD: could not launch %s\r\n", ret, "button thread");
    	xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
    	return (void*) -3;
    }
    ret = pthread_create (&switches, &attr, (void*) switches_thread, NULL);
    if (ret != 0)
    {
    	xil_printf("ERROR (%d) IN MASTER THREAD: could not launch %s\r\n", ret, "switches thread");
    	xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
    	return (void*) -3;
    }
    ret = pthread_create (&leds, &attr, (void*) leds_thread, NULL);
    if (ret != 0)
    {
    	xil_printf("ERROR (%d) IN MASTER THREAD: could not launch %s\r\n", ret, "switches thread");
    	xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
    	return (void*) -3;
    }

    // initialize the button press semaphore
	ret = sem_init (&btn_press_sema, 0, 0);
    if (ret != 0)
    {
    	xil_printf("ERROR (%d) IN MASTER THREAD: could not initialize %s\r\n", errno, "button press semaphore");
    	xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
    	return (void*) -3;
    }
	else
	{
		xil_printf ("MASTER: Button press semaphore has been initialized\n\r");
	}

	// Register the interrupt handlers
    ret = register_int_handler(WDT_INTR_NUM, (void*) wdt_handler, NULL);
    if (ret != XST_SUCCESS)
    {
    	return (void*) -4;
    }
    ret = register_int_handler(BTN_GPIO_INTR_NUM, (void*) button_handler, NULL);
    if (ret != XST_SUCCESS)
    {
    	return (void*) -4;
    }

    // Enable interrupts and start the WDT...we're off to the races
    enable_interrupt(BTN_GPIO_INTR_NUM);
	enable_interrupt(WDT_INTR_NUM);
	xil_printf("MASTER: Interrupts have been enabled\r\n");
	
	XWdtTb_Start(&WDTInst);
	xil_printf("MASTER: Watchdog timer has been started\r\n");

	// master thread main loop
	while(1)
	{
        isApplicationRunning = true;
        sleep(500);
	}
	
	return NULL;
}

// The button thread
void* button_thread(void *arg)
{
	while(1){
		if (button_state){
			xil_printf("Button Pressed %d\r\n",button_state);
			button_state = 0;
		}
		sleep(1);
	}
	return NULL;
}

// The switches thread
void* switches_thread(void *arg)
{
	while(1){
		u32 switch_state = XGpio_DiscreteRead(&SWInst,1);
        if (true == switch_state & 0xFF){
            xil_printf("System Freeze!!!\r\n");
            forceFreeze = true;
        }

	    int msgqueue;

    	// Message queue initialization
    	// if the argument key does not already have a message queue identifier 
        // associated with it, and (msgflg and IPC_CREAT) is non-zero.
    	msgqueue = msgget(led_msg_key, IPC_CREAT);

    	//-1 on failure and sets errno appropriately; errno can be set
    	if(msgqueue < 0)
        {
           xil_printf("\r\n Error in MQ in LEDSwitch thread");
    	   indicate_error = errno;
        }

    	// update the LED message struct
    	newMessagequeue.msg_src = 2;
    	newMessagequeue.msg_value = switch_state;

    	//send data to ledthreads
    	msgsnd(msgqueue, &newMessagequeue, sizeof(t_LED_message), 0);
    	xil_printf ("Queue=%d\r\n",newMessagequeue.msg_value);
    	// Terminate
    	msgctl(msgqueue, IPC_RMID, NULL);
        sleep(1);
    }
    return NULL;
}

// The leds thread
void* leds_thread(void *arg)
{   
    u32 led_sequence = 0;

	while(1){
        // XGpio_DiscreteWrite(&LEDInst, 1, led_sequence);
        // led_sequence++;
        // if (led_sequence == 8)
        //     led_sequence = 0;

    	int msgqueue;
    	int msgqueue_write;
    	//For debugging
    	xil_printf("Inside Leds Thread\r\n");

    	//This function returns the message queue identifier associated with the argument key.
    	msgqueue = msgget(led_msg_key, IPC_CREAT);

    	if(msgqueue < 0){
    		xil_printf("Error in MQ in LED\r\n");
    		indicate_error = errno;
        }

    	//This function reads a message from the queue associated with the message queue identifier
    	msgrcv(msgqueue, &newMessagequeue, sizeof(t_LED_message),0,0);

    	//Check Whether its from switch or button 1 for switch and 2 for pushbutton
    	 msgqueue_write = newMessagequeue.msg_value;
    	 switch (newMessagequeue.msg_src)
    	 {
    		case 1:
    		 	XGpio_DiscreteWrite(&LEDInst,XPIO_CHANNEL,msgqueue_write);
    		    xil_printf ("Value LED writes=%d\r\n",msgqueue_write);
    		    break;
    		case 2:
    			XGpio_DiscreteWrite(&LEDInst,XPIO_CHANNEL,msgqueue_write);
    			xil_printf ("Value LED writes=%d\r\n",msgqueue_write);
    			break;
    		default:
    			break;
    	}
	    sleep(10);
	}
	return NULL;
}

// ----------------------init_peripherals() - Initializes the peripherals-------------------------
XStatus init_peripherals(void)
{
	XStatus Status; // status from Xilinx Lib calls
	XWdtTb_Initialize(&WDTInst, WDT_DEVICEID);

	// initialize the Buttons
    Status = XGpio_Initialize(&BTNInst, BTN_GPIO_DEVICEID);
    if (Status != XST_SUCCESS) {
        return XST_FAILURE;
    }
    // set direction to input
    XGpio_SetDataDirection(&BTNInst, 1, 0xFF);
    XGpio_InterruptEnable(&BTNInst, 0xFF);
    XGpio_InterruptGlobalEnable(&BTNInst);

    // initialize the Switches
    Status = XGpio_Initialize(&SWInst, SW_GPIO_DEVICEID);
    if (Status != XST_SUCCESS) {
        return XST_FAILURE;
    }
    // set direction to input
    XGpio_SetDataDirection(&SWInst, 1, 0xFF);

    // initialize the Buttons
    Status = XGpio_Initialize(&BTNInst, LED_GPIO_DEVICEID);
    if (Status != XST_SUCCESS) {
        return XST_FAILURE;
    }

	return XST_SUCCESS;
}

//--------------button press interrupt handler--------------------
void button_handler(void)
{
	button_state = XGpio_DiscreteRead(&BTNInst,1);
	acknowledge_interrupt(BTN_GPIO_INTR_NUM);
}

// WDT interrupt handler
void wdt_handler(void)
{
    // Check if application is running and not in force freeze state
    if (false == forceFreeze && true == isApplicationRunning) {
        XWdtTb_RestartWdt(&WDTInst);    // Restart WDT
        isApplicationRunning = false;   // Reset isApplicationRunning flag
    }
	acknowledge_interrupt(WDT_INTR_NUM);
}
